use std::time::Duration;

use axum::{
    extract::{Query, State},
    http::StatusCode,
    Json,
};
use serde::{Deserialize, Serialize};

use crate::{
    config::{validate_token, Config},
    monitor::{
        collect_system_stats, dns_lookup, http_bench, parse_ports, ping, portscan, tcping,
        ws_bench, DnsOptions, DnsReport, HttpBenchOptions, HttpBenchReport, NicStats, PingOptions,
        PingReport, PortscanOptions, PortscanReport, ProbeEnvelope, SystemStats, TcpingOptions,
        TcpingReport, WsBenchOptions, WsBenchReport,
    },
};
use serde_json::json;

use super::{AppState, AuthQuery};

#[derive(Serialize)]
pub struct ApiResponse<T> {
    code: u16,
    message: String,
    data: Option<T>,
}

impl<T> ApiResponse<T> {
    fn success(data: T) -> Self {
        Self {
            code: StatusCode::OK.as_u16(),
            message: "success".into(),
            data: Some(data),
        }
    }

    fn error(status: StatusCode, message: &str) -> (StatusCode, Json<Self>) {
        (
            status,
            Json(Self {
                code: status.as_u16(),
                message: message.into(),
                data: None,
            }),
        )
    }
}

pub async fn get_system_stats(
    State(state): State<AppState>,
    Query(query): Query<AuthQuery>,
) -> (StatusCode, Json<ApiResponse<SystemStats>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    let stats = collect_system_stats();
    (StatusCode::OK, Json(ApiResponse::success(stats)))
}

pub async fn get_network_stats(
    State(state): State<AppState>,
    Query(query): Query<AuthQuery>,
) -> (StatusCode, Json<ApiResponse<Vec<NicStats>>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    let stats = state.monitor.collect_stats();
    (StatusCode::OK, Json(ApiResponse::success(stats)))
}

#[derive(Deserialize)]
pub struct PingQuery {
    pub token: Option<String>,
    pub target: String,
    #[serde(default = "default_ping_count")]
    pub count: u32,
    #[serde(default = "default_ping_timeout")]
    pub timeout_ms: u64,
}

fn default_ping_count() -> u32 {
    4
}

fn default_ping_timeout() -> u64 {
    1000
}

#[derive(Deserialize)]
pub struct TcpingQuery {
    pub token: Option<String>,
    pub target: String,
    pub port: u16,
    #[serde(default = "default_tcping_count")]
    pub count: u32,
    #[serde(default = "default_tcping_timeout")]
    pub timeout_ms: u64,
}

fn default_tcping_count() -> u32 {
    4
}

fn default_tcping_timeout() -> u64 {
    1000
}

#[derive(Deserialize)]
pub struct DnsQuery {
    pub token: Option<String>,
    pub domain: String,
    #[serde(default = "default_dns_record_type")]
    pub record_type: String,
}

fn default_dns_record_type() -> String {
    "A".into()
}

#[derive(Deserialize)]
pub struct PortscanQuery {
    pub token: Option<String>,
    pub target: String,
    pub ports: String,
    #[serde(default = "default_portscan_timeout")]
    pub timeout_ms: u64,
    #[serde(default = "default_portscan_concurrency")]
    pub concurrency: usize,
}

fn default_portscan_timeout() -> u64 {
    500
}

fn default_portscan_concurrency() -> usize {
    50
}

#[derive(Deserialize)]
pub struct HttpBenchQuery {
    pub token: Option<String>,
    pub url: String,
    #[serde(default = "default_http_requests")]
    pub requests: u32,
    #[serde(default = "default_http_concurrency")]
    pub concurrency: usize,
    #[serde(default = "default_http_timeout")]
    pub timeout_ms: u64,
}

fn default_http_requests() -> u32 {
    100
}

fn default_http_concurrency() -> usize {
    10
}

fn default_http_timeout() -> u64 {
    1000
}

#[derive(Deserialize)]
pub struct WsBenchQuery {
    pub token: Option<String>,
    pub url: String,
    #[serde(default = "default_ws_connections")]
    pub connections: u32,
    #[serde(default = "default_ws_concurrency")]
    pub concurrency: usize,
    #[serde(default = "default_ws_messages")]
    pub messages: u32,
    pub payload: Option<String>,
    #[serde(default = "default_ws_timeout")]
    pub timeout_ms: u64,
}

fn default_ws_connections() -> u32 {
    50
}

fn default_ws_concurrency() -> usize {
    10
}

fn default_ws_messages() -> u32 {
    0
}

fn default_ws_timeout() -> u64 {
    1000
}

pub async fn ping_handler(
    State(state): State<AppState>,
    Query(query): Query<PingQuery>,
) -> (StatusCode, Json<ApiResponse<ProbeEnvelope<PingReport>>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    let envelope = ping(PingOptions {
        target: &query.target,
        count: query.count,
        timeout: Duration::from_millis(query.timeout_ms),
    })
    .await;
    (StatusCode::OK, Json(ApiResponse::success(envelope)))
}

pub async fn tcping_handler(
    State(state): State<AppState>,
    Query(query): Query<TcpingQuery>,
) -> (StatusCode, Json<ApiResponse<ProbeEnvelope<TcpingReport>>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    let envelope = tcping(TcpingOptions {
        target: &query.target,
        port: query.port,
        count: query.count,
        timeout: Duration::from_millis(query.timeout_ms),
    })
    .await;
    (StatusCode::OK, Json(ApiResponse::success(envelope)))
}

pub async fn dns_handler(
    State(state): State<AppState>,
    Query(query): Query<DnsQuery>,
) -> (StatusCode, Json<ApiResponse<ProbeEnvelope<DnsReport>>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    let envelope = dns_lookup(DnsOptions {
        query: &query.domain,
        record_type: &query.record_type,
    })
    .await;
    (StatusCode::OK, Json(ApiResponse::success(envelope)))
}

pub async fn portscan_handler(
    State(state): State<AppState>,
    Query(query): Query<PortscanQuery>,
) -> (StatusCode, Json<ApiResponse<ProbeEnvelope<PortscanReport>>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    let ports = match parse_ports(&query.ports) {
        Ok(ports) => ports,
        Err(err) => return ApiResponse::error(StatusCode::BAD_REQUEST, &err),
    };

    let envelope = portscan(PortscanOptions {
        target: &query.target,
        ports,
        timeout: Duration::from_millis(query.timeout_ms),
        concurrency: query.concurrency.max(1),
    })
    .await;
    (StatusCode::OK, Json(ApiResponse::success(envelope)))
}

pub async fn http_bench_handler(
    State(state): State<AppState>,
    Query(query): Query<HttpBenchQuery>,
) -> (
    StatusCode,
    Json<ApiResponse<ProbeEnvelope<HttpBenchReport>>>,
) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    let envelope = http_bench(HttpBenchOptions {
        url: &query.url,
        requests: query.requests,
        concurrency: query.concurrency,
        timeout_ms: query.timeout_ms,
    })
    .await;

    (StatusCode::OK, Json(ApiResponse::success(envelope)))
}

#[derive(Serialize)]
pub struct StatusPayload {
    pub running: bool,
    pub uptime_ms: u128,
    pub http_port: u16,
    pub interval_ms: u64,
}

pub async fn admin_status_handler(
    State(state): State<AppState>,
    Query(query): Query<AuthQuery>,
) -> (StatusCode, Json<ApiResponse<StatusPayload>>) {
    let token = query.token.as_deref().unwrap_or("");
    let (http_port, interval_ms) = {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
        (config.server.http_port, config.server.interval_ms)
    };

    let data = StatusPayload {
        running: true,
        uptime_ms: state.start_time.elapsed().as_millis(),
        http_port,
        interval_ms,
    };

    (StatusCode::OK, Json(ApiResponse::success(data)))
}

pub async fn admin_reload_handler(
    State(state): State<AppState>,
    Query(query): Query<AuthQuery>,
) -> (StatusCode, Json<ApiResponse<serde_json::Value>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    match Config::load() {
        Ok(new_config) => {
            let http_port = new_config.server.http_port;
            let interval_ms = new_config.server.interval_ms;
            {
                let mut guard = state.config.write().await;
                *guard = new_config;
            }
            let body = json!({
                "http_port": http_port,
                "interval_ms": interval_ms,
                "note": "port changes require a restart to take effect"
            });
            return (StatusCode::OK, Json(ApiResponse::success(body)));
        }
        Err(err) => ApiResponse::error(
            StatusCode::INTERNAL_SERVER_ERROR,
            &format!("failed to reload config: {err}"),
        ),
    }
}

pub async fn admin_stop_handler(
    State(state): State<AppState>,
    Query(query): Query<AuthQuery>,
) -> (StatusCode, Json<ApiResponse<serde_json::Value>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    match state.shutdown_tx.send(super::ServerCommand::Stop) {
        Ok(_) => (
            StatusCode::OK,
            Json(ApiResponse::success(json!({ "status": "stopping" }))),
        ),
        Err(err) => ApiResponse::error(
            StatusCode::INTERNAL_SERVER_ERROR,
            &format!("failed to signal shutdown: {err}"),
        ),
    }
}

pub async fn ws_bench_handler(
    State(state): State<AppState>,
    Query(query): Query<WsBenchQuery>,
) -> (StatusCode, Json<ApiResponse<ProbeEnvelope<WsBenchReport>>>) {
    let token = query.token.as_deref().unwrap_or("");
    {
        let config = state.config.read().await;
        if !validate_token(&config, token) {
            return ApiResponse::error(StatusCode::UNAUTHORIZED, "invalid token");
        }
    }

    let envelope = ws_bench(WsBenchOptions {
        url: &query.url,
        connections: query.connections,
        concurrency: query.concurrency,
        messages: query.messages,
        payload: query.payload.as_deref(),
        timeout_ms: query.timeout_ms,
    })
    .await;

    (StatusCode::OK, Json(ApiResponse::success(envelope)))
}
